package cn.akwangl.concurrency.web.model.order.service.impl;

import cn.akwangl.concurrency.web.common.lock.RedisLock;
import cn.akwangl.concurrency.web.exception.BizException;
import cn.akwangl.concurrency.web.model.order.pojo.OrderVo;
import cn.akwangl.concurrency.web.model.order.pojo.SubmitOrderDto;
import cn.akwangl.concurrency.web.model.order.service.CreateOrdersService;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * redis 方式解决线程安全问题.
 *
 * @author 阿坤同学
 * @since 2022-11-26 22:24:53
 */
@Service("redisLockOrdersService")
public class RedisLockOrdersServiceImpl implements CreateOrdersService {

    private CreateOrdersService createOrdersService;

    private RedisLock redisLock;

    @Autowired
    @Qualifier("createOrdersService")
    public void setCreateOrdersService(CreateOrdersService createOrdersService) {
        this.createOrdersService = createOrdersService;
    }

    @Autowired
    public void setRedisLock(RedisLock redisLock) {
        this.redisLock = redisLock;
    }

    /**
     * 该方法使用redis 手写分布式锁来解决商品超卖问题{@link RedisLock}.
     * 在方法中有三个变量lastExp(最大重试获取锁等待时间)、retryNum(重试计数器)、retryMax(最大重试次数),当某个线程执行次数超过retryMax时则
     * 会休眠100毫秒,然后唤醒所有等待线程来争抢锁.当当前时间超过lastExp时则订单创建失败.
     *
     * @param submitOrderDto 创建订单包装类
     * @return 订单信息 为null则代表创建失败
     * @author 阿坤同学
     * @date 2022/11/26 23:05
     */
    @Override
    public OrderVo createOrder(SubmitOrderDto submitOrderDto) {
        String redisKey = "order_lock";
        String redisValue = IdWorker.getIdStr();

        int retryNum = 0;
        int retryMax = 100;
        long lastExp = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(10);
        try {
            while (System.currentTimeMillis() <= lastExp) {
                if (redisLock.getLock(redisKey, redisValue, 3, TimeUnit.SECONDS)) {
                    return createOrdersService.createOrder(submitOrderDto);
                }
                retryNum++;
                if (retryMax == retryNum) {
                    try {
                        retryNum = 0;
                        synchronized (this) {
                            wait(100L);
                        }
                    } catch (InterruptedException e) {
                        throw new BizException("创建订单失败,请重试");
                    }
                }
            }
        } finally {
            redisLock.unlock(redisKey, redisValue);
        }
        return null;
    }
}
